/*
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package ru.papergames.battleserver.network;

import org.apache.log4j.Logger;
import org.mmocore.network.MMOClient;
import org.mmocore.network.MMOConnection;
import ru.papergames.battleserver.BattleThreadPoolManager;
import ru.papergames.battleserver.model.PaperGameFlashUser;
import ru.papergames.battleserver.model.UsersManager;
import ru.papergames.battleserver.model.WebServerClientHolder;
import ru.papergames.battleserver.model.battle.BattlePlayer;
import ru.papergames.battleserver.network.serverpackets.flashuser.PaperGameServerPacket;
import ru.papergames.battleserver.network.serverpackets.webserver.FlashUserDisconnected;

import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledFuture;

/**
 * Represents a client connected on Game Server
 *
 * @author KenM
 */
public final class PaperGameClient extends MMOClient<MMOConnection<PaperGameClient>> {
    protected static final Logger log = Logger.getLogger(PaperGameClient.class.getName());

    /**
     * CONNECTED	- client has just connected
     * AUTHED		- client has authed
     * IN_BATTLE	- client is in battle
     *
     * @author KenM
     */
    public static enum GameClientState {
        CONNECTED, AUTHED, IN_BATTLE
    }

    private PaperGameFlashUser user;
    public GameClientState state;

    // Info
    private String accountName;
//    private SessionKey _sessionId;
//    @Deprecated
//    private ReentrantLock activeCharLock = new ReentrantLock();

    private boolean isAuthedGG;
    private long connectionStartTime;

    // Task
    protected ScheduledFuture<?> cleanupTask = null;

    // Crypt
    private GameCrypt crypt;

    // Flood protection
    public byte packetsSentInSec = 0;
    public long packetsSentStartTick = 0;

    private boolean detached = false;

    private boolean protocol;

    public PaperGameClient(MMOConnection<PaperGameClient> con) {
        super(con);
        state = GameClientState.CONNECTED;
        connectionStartTime = System.currentTimeMillis();
        crypt = new GameCrypt();
    }

    public PaperGameFlashUser getUser() {
        return user;
    }

    public void setUser(PaperGameFlashUser user) {
        this.user = user;
    }

    public byte[] enableCrypt() {
        byte[] key = BlowFishKeygen.getRandomKey();
        crypt.setKey(key);
        return key;
    }

    public GameClientState getState() {
        return state;
    }

    public void setState(GameClientState pState) {
        state = pState;
    }

    public long getConnectionStartTime() {
        return connectionStartTime;
    }

    @Override
    public boolean decrypt(ByteBuffer buf, int size) {
        crypt.decrypt(buf.array(), buf.position(), size);
        return true;
    }

    @Override
    public boolean encrypt(final ByteBuffer buf, final int size) {
        crypt.encrypt(buf.array(), buf.position(), size);
        buf.position(buf.position() + size);
        return true;
    }

    public void setGameGuardOk(boolean val) {
        isAuthedGG = val;
    }

    public boolean isAuthedGG() {
        return isAuthedGG;
    }

    public void setAccountName(String pAccountName) {
        accountName = pAccountName;
    }

    public String getAccountName() {
        return accountName;
    }

//    public void setSessionId(SessionKey sk) {
//        _sessionId = sk;
//    }
//
//    public SessionKey getSessionId() {
//        return _sessionId;
//    }
//
    public void sendPacket(PaperGameServerPacket gsp) {
        if (detached) return;

        // Packets from invisible chars sends only to GMs
//        if (gsp.isInvisible() && getActiveChar() != null && !getActiveChar().isGM())
//            return;

        getConnection().sendPacket(gsp);
        gsp.runImpl();
    }

    public boolean isDetached() {
        return detached;
    }

    public void isDetached(boolean b) {
        detached = b;
    }

    public void close(PaperGameServerPacket gsp) {
        getConnection().close(gsp);
    }

    @Override
    protected void onForcedDisconnection() {
        log.info("Client " + toString() + " disconnected abnormally.");
    }

    @Override
    protected void onDisconnection() {
        // no long running tasks here, do it async
        try {
            BattleThreadPoolManager.getInstance().executeDisconnectTask(new DisconnectTask());
        } catch (RejectedExecutionException e) {
            // server is closing
        }
    }

/*
    public void closeNow() {
        super.getConnection().close(null);
        cleanMe(true);
    }
*/

    /**
     * Produces the best possible string representation of this client.
     */
    @Override
    public String toString() {
        try {
            InetAddress address = getConnection().getInetAddress();
            switch (getState()) {
                case CONNECTED:
                    return "[IP: " + (address == null ? "disconnected" : address.getHostAddress()) + "]";
                case AUTHED:
                    return "[Account: " + getAccountName() + " - IP: " + (address == null ? "disconnected" : address.getHostAddress()) + "]";
                case IN_BATTLE:
                    return "[BattleId:: " + (user.getBattleMember() == null ? "disconnected" : user.getBattle().getBattleOrderId()) + " - Account: " + getAccountName() + " - IP: " + (address == null ? "disconnected" : address.getHostAddress()) + "]";
                default:
                    throw new IllegalStateException("Missing state on switch");
            }
        }
        catch (NullPointerException e) {
            return "[Character read failed due to disconnect]";
        }
    }

    class DisconnectTask implements Runnable {

        public void run() {


            PaperGameFlashUser userTmp = user;
            if (userTmp != null) {
                System.out.println("PaperGameClient$DisconnectTask.run[" + userTmp.getId() + "]");
                WebServerClientHolder.getInstance().sendPacketToWebServer(new FlashUserDisconnected(userTmp));
                UsersManager.getInstance().onUserDisconnection(userTmp);
            } else {
                System.out.println("PaperGameClient$DisconnectTask.run USER IS NULL");
            }


            boolean fast = true;

            try {
                isDetached(true);
                if (userTmp != null && userTmp.getBattle() != null) {
                    log.info("Disconnecting player[" + userTmp.getId() + "] from battle server");
                    BattlePlayer battleMember = userTmp.getBattleMember();
                    userTmp.getBattle().onPlayerDisconnect(userTmp.getBattleMember());

                    battleMember.setConnected(false);
                    battleMember.setGameUser(null);

                    userTmp.setBattle(null);
                    userTmp.setBattleMember(null);
                } else {
                    log.info("Disconnecting oside game client[no bettale member found] from battle server");
                }
            } catch (Exception e1) {
                log.warn("Error while disconnecting client.", e1);
            }
        }
    }

    public boolean isProtocolOk() {
        return protocol;
    }

    public void setProtocolOk(boolean b) {
        protocol = b;
	}
}
